/*
 * DexOptimize.c
 *
 *  Created on: 2011-7-9
 *      Author: gexiao
 */
#include "../Dalvik.h"
#include "../../libdex/OptInvocation.h"

#include <sys/stat.h>
#include <sys/file.h>
#include <fcntl.h>
#include <assert.h>
#include <stdbool.h>

/*
 *
 */bool dvmUnlockCachedDexFile(int fd) {

}

/*
 * Return the fd of an open file in the DEX file cache area. If the cache
 * file doesn't exist or is out of date, this will remove the old entry,
 * create a new one (writing only the file header), and return with the
 * "new file" flag set.
 *
 * ...
 */
int dvmOpenCachedDexFile(const char* fileName, const char* subFileName,
		u4 modWhen, u4 crc, bool isBootstrap, char** pCachedName,
		bool* pNewFile, bool createIfMissing) {
	char cacheFileName[512];
	int fd, cc;
	struct stat fdStat, fileStat;
	bool readOnly = false;

	*pNewFile = false;

	if (!dexOptGenerateCacheFileName(fileName, subFileName, cacheFileName,
			sizeof(cacheFileName)))
		return -1;
	retry: fd = createIfMissing ? open(cacheFileName, O_CREAT | O_RDWR, 0644)
			: -1;
	if (fd < 0) {
		fd = open(cacheFileName, O_RDONLY, 0);
		if (fd < 0) {
			if (createIfMissing) {
			}
			return fd;
		}
		readOnly = true;
	}

	/*
	 * Grab an exclusive lock on the cache file. If somebody else is working
	 * on it, we'll block here until they complete. Because
	 * we're waiting on an external resource, we go into VMWAIT mode.
	 */
	int oldStatus;
	oldStatus = dvmChangeStatus(NULL, THREAD_VMWAIT);
	cc = flock(fd, LOCK_EX | LOCK_NB);
	if (cc != 0)
		cc = flock(fd, LOCK_EX);
	dvmChangeStatus(NULL, oldStatus);
	if (cc != 0) {
		close(fd);
		return -1;
	}

	/*
	 * Check to see if the fd we opened and locked matches the file in
	 * the filesystem. If they don't, then somebody else unlinked ours
	 * and created a new file, and we need to use that one instead. (If
	 * we caught them between the unlink and the create, we'll get an
	 * ENOENT from the file stat.)
	 */
	cc = fstat(fd, &fdStat);
	if (cc != 0) {
		goto close_fail;
	}
	cc = stat(cacheFileName, &fileStat);
	if (cc != 0 || fdStat.st_dev != fileStat.st_dev || fdStat.st_ino
			!= fileStat.st_ino) {
		flock(fd, LOCK_UN);
		close(fd);
		usleep(250 * 1000); //if something is hosted, don't peg machine
		goto retry;
	}

	/*
	 * We have the correct file open and locked. If the file size is zero,
	 * then it was just created by us, and we want to fill in some fields
	 * in the "opt" header and set "*pNewFile". Otherwise, we want to
	 * verify that the fields in the header match our expectations, and
	 * reset the file if they don't.
	 */
	if (fdStat.st_size == 0) {
		if (readOnly)
			goto close_fail;
		cc = dexOptCreateEmptyHeader(fd);
		if (cc != 0)
			goto close_fail;
		*pNewFile = true;
	} else {
		bool expectVerify, expectOpt;

		if (gDvm.classVerifyMode == VERIFY_MODE_NONE)
			expectVerify = false;
		else if (gDvm.classVerifyMode == VERIFY_MODE_REMOTE)
			expectVerify = !isBootstrap;
		else
			expectVerify = true;

		if (gDvm.dexOptMode ==OPTIMIZE_MODE_NONE)
			expectOpt = false;
		else if (gDvm.dexOptMode == OPTIMIZE_MODE_VERIFIED)
			expectOpt = expectVerify;
		else
			expectOpt = true;
		if (!dvmCheckOptHeaderAndDependencies(fd, true, modWhen, crc,
				expectVerify, expectOpt)) {
			if (readOnly) {
				/*
				 * We could unlink and rewrite the file if we own it or
				 * the "sticky" bit isn't set on the directory.  However,
				 * we're not able to truncate it, which spoils things.  So,
				 * give up now.
				 */
				if (createIfMissing) {
				}
				goto close_fail;
			}

			/*
			 * If we truncate the existing file before unlinking it, any
			 * process that has it mapped will fail when it tries to touch
			 * the pages.
			 *
			 * This is very important.  The zygote process will have the
			 * boot DEX files (core, framework, etc.) mapped early.  If
			 * (say) core.dex gets updated, and somebody launches an app
			 * that uses App.dex, then App.dex gets reoptimized because it's
			 * dependent upon the boot classes.  However, dexopt will be
			 * using the *new* core.dex to do the optimizations, while the
			 * app will actually be running against the *old* core.dex
			 * because it starts from zygote.
			 *
			 * Even without zygote, it's still possible for a class loader
			 * to pull in an APK that was optimized against an older set
			 * of DEX files.  We must ensure that everything fails when a
			 * boot DEX gets updated, and for general "why aren't my
			 * changes doing anything" purposes its best if we just make
			 * everything crash when a DEX they're using gets updated.
			 */
			if (ftruncate(fd, 0) != 0) {
				/* keep going */
			}
			if (unlink(cacheFileName) != 0) {
				/* keep going; permission failure should probably be fatal */
			}
			flock(fd, LOCK_UN);
			close(fd);
			goto retry;
		} else {
		}
	}

	*pCachedName = strdup(cacheFileName);
	assert(fd >= 0);
	return fd;

	close_fail: flock(fd, LOCK_UN);
	close(fd);
	return -1;
}

/*
 * Get the cache file name from a ClassPathEntry.
 */
static const char* getCacheFileName(const ClassPathEntry* cpe) {
	switch (cpe->kind) {
	case kCpeJar:
		return dvmGetJarFileCacheFileName((JarFile*) cpe->ptr);
	case kCpeDex:
		return dvmGetRawDexFileCacheFileName((RawDexFile*) cpe->ptr);
	default:
		//LOGE("DexOpt: unexpected cpe kind %d\n", cpe->kind);
		dvmAbort();
		return NULL;
	}
}

/*
 * Get the SHA-1 signature.
 */
static const u1* getSignature(const ClassPathEntry* cpe) {
	DvmDex* pDvmDex;

	switch (cpe->kind) {
	case kCpeJar:
		pDvmDex = dvmGetJarFileDex((JarFile*) cpe->ptr);
		break;
	case kCpeDex:
		pDvmDex = dvmGetRawDexFileDex((RawDexFile*) cpe->ptr);
		break;
	default:
		//LOGE("unexpected cpe kind %d\n", cpe->kind);
		dvmAbort();
		pDvmDex = NULL; // make gcc happy
	}

	assert(pDvmDex != NULL);
	return pDvmDex->pDexFile->pHeader->signature;
}

/*
 * Dependency layout:
 * 	4b Source file modification time, in seconds since 1970 UTC
 * 	4b CRC-32 from Zip entry,or Adler32 from source DEX header
 * 	4b Dalvik VM build number
 * 	4b Number of dependency entries that follow
 * 	Dependency entries:
 * 		4b Name length (including terminating null)
 * 		var Full path of cache entry (null terminated)
 * 		20b SHA-1 signature from source DEX file
 * 	If this changes,update DEX_OPT_MAGIC_VERS.
 */
static const size_t kMinDepSize = 4 * 4;
static const size_t kMaxDepSize = 4 * 4 + 2048; //sanity check
/*
 * Read the "opt" header, verify it, then read the dependencies section
 * and verify that data as well.
 *
 * If "sourceAvail" is "true", this will verify that "modWhen" and "crc"
 * match up with what is stored in the header. If they don't , we reject
 * the file so that it can be recreated from the updated original. If
 * "sourceAvail" isn't set, e.g. for a .odex file, we ignore these arguments.
 *
 * On successful return, the file will be seeked immediately past the "opt"
 * header.
 */
bool dvmCheckOptHeaderAndDependencies(int fd, bool sourceAvail, u4 modWhen,
		u4 crc, bool expectVerify, bool expectOpt) {
	DexOptHeader optHdr;
	u1* depData = NULL;
	const u1* magic;
	off_t posn;
	bool result = false;
	ssize_t actual;

	/*
	 * Start at the start. The "opt" header, when present, will always be the first
	 * thing in the file.
	 */
	if (lseek(fd, 0, SEEK_SET) != 0) {
		//LOGE("DexOpt: failed to seek to start of file: %s\n", strerror(errno));
		goto bail;
	}

	/*
	 * Read and do trivial verification on the opt header. The header is
	 * always in host byte order.
	 */
	actual = read(fd, &optHdr, sizeof(optHdr));
	if (actual < 0) {
		//LOGE("DexOpt: failed reading opt header: %s\n", strerror(errno));
		goto bail;
	} else if (actual != sizeof(optHdr)) {
		//LOGE("DexOpt: failed reading opt header (got %d of %zd)\n",
		//	(int) actual, sizeof(optHdr));
		goto bail;
	}

	magic = optHdr.magic;
	if (memcmp(magic, DEX_MAGIC, 4) == 0) {
		/* somebody probably pointed us at the wrong file */
		//LOGD("DexOpt: expected optimized DEX, found unoptimized\n");
		goto bail;
	} else if (memcmp(magic, DEX_OPT_MAGIC, 4) != 0) {
		/* not a DEX file, or previous attempt was interrupted */
		//LOGD("DexOpt: incorrect opt magic number (0x%02x %02x %02x %02x)\n",
		//  magic[0], magic[1], magic[2], magic[3]);
		goto bail;
	}
	if (memcmp(magic + 4, DEX_OPT_MAGIC_VERS, 4) != 0) {
		goto bail;
	}
	if (optHdr.depsLength < kMinDepSize || optHdr.depsLength > kMaxDepSize) {
		goto bail;
	}
	/*
	 * Do the header flags match up with what we want?
	 *
	 * This is useful because it allows us to automatically regenerate
	 * a file when settings change (e.g. verification is now mandatory),
	 * but can cause difficulties if the bootstrap classes we depend upon
	 * were handled differently than the current options specify. We get
	 * upset because they're not verified or optimized, but we're not able
	 * to regenerate them because the installer won't let us.
	 *
	 * (This is also of limited value when !sourceAvail.)
	 *
	 * So, for now, we essentially ignore "expectVerify" and "expectOpt"
	 * by limiting the match mask.
	 *
	 * The only thing we really can't handle is incorrect byte-ordering.
	 */
	const u4 matchMask = DEX_OPT_FLAG_BIG;
	u4 expectedFlags = 0;
#if __BYTE_ORDER!=__LITTLE_ENDIAN
	expectedFlags |= DEX_OPT_FLAG_BIG;
#endif
	if (expectVerify)
		expectedFlags |= DEX_FLAG_VERIFIED;
	if (expectOpt)
		expectedFlags |= DEX_OPT_FLAG_FIELDS | DEX_OPT_FLAG_INVOCATIONS;
	if ((expectedFlags & matchMask) != (optHdr.flags & matchMask)) {
		goto bail;
	}

	posn = lseek(fd, optHdr.depsOffset, SEEK_SET);
	if (posn < 0) {
		goto bail;
	}

	/*
	 * Read all of the dependency stuff into memory.
	 */
	depData = (u1*) malloc(optHdr.depsLength);
	if (depData == NULL) {
		goto bail;
	}
	actual = read(fd, depData, optHdr.depsLength);
	if (actual < 0) {
		//LOGW("DexOpt: failed reading deps: %s\n", strerror(errno));
		goto bail;
	} else if (actual != (ssize_t) optHdr.depsLength) {
		//LOGW("DexOpt: failed reading deps: got %d of %d\n",
		//(int) actual, optHdr.depsLength);
		goto bail;
	}

	/*
	 * Verify simple items.
	 */
	const u1* ptr;
	u4 val;

	ptr = depData;
	val = read4LE(&ptr);
	if (sourceAvail && val != modWhen) {
		goto bail;
	}
	val = read4LE(&ptr);
	if (sourceAvail && val != crc) {
		goto bail;
	}
	val = read4LE(&ptr);
	if (val != DALVIK_VM_BUILD) {
		goto bail;
	}

	/*
	 * Verify dependencies on other cached DEX files. It must match
	 * exactly with what is currently defined in the bootclasspath.
	 */
	ClassPathEntry* cpe;
	u4 numDeps;

	numDeps = read4LE(&ptr);

	for (cpe = gDvm.bootClassPath; cpe->ptr != NULL; cpe++) {
		const char* cacheFileName = dvmPathToAbsolutePortion(
				getCacheFileName(cpe));
		const u1* signature = getSignature(cpe);
		size_t len = strlen(cacheFileName) + 1;
		u4 storedStrLen;

		if (numDeps == 0) {
			goto bail;
		}

		storedStrLen = read4LE(&ptr);
		if (len != storedStrLen || strcmp(cacheFileName, (const char*) ptr)
				!= 0) {
			goto bail;
		}

		ptr += storedStrLen;

		if (memcmp(signature, ptr, kSHA1DigestLen) != 0) {
			goto bail;
		}
		ptr += kSHA1DigestLen;

		numDeps--;
	}

	if (numDeps != 0) {
		goto bail;
	}

	//consumed all data and no more?
	if (ptr != depData + optHdr.depsLength) {
		assert(false);
	}
	result = true;

	bail: free(depData);
	return result;
}

